Repository navigation
Feat/zpl mu units of measure - #128
Conversation
Parser normalizes ^MUI/^MUM coords to dots at read-time via a new intDots/intDotsOrUndef helper threaded through ~30 dot-quantity sites (FO/FT, GB/GC/GD/GE/GS, A/CF/FB/FP/LH/LT/TB, BY + all bcHeight, PW/LL/ LS/ML). intDots uses parseFloat so fractional inch/mm values (`^MUI^FO 0.5,0.5`) survive instead of getting parseInt-truncated to zero. ^MU `a`-slot scope follows the spec: carries field-by-field until overridden, no ^XA reset. Per-block isolation in production comes from zplImportService calling parseZPL per ^XA block. b/c slots persist on labelConfig.formatDpi/outputDpi and re-emit as `^MUD,b,c` only when both are set (paired directive; no synthesized half-set ratio). Invalid `a` (e.g. `^MUX`) and out-of-spec b/c surface as a partial import finding instead of silently normalizing.
Curried dot-helper plumbing:
- helpers.ts gains `dotsFor(state)` which binds intDots/intDotsOrUndef
to a state's live unitScale. Replaces a per-factory closure that
was duplicated across fields/barcodes/graphics/labelConfig.
- intDots collapses to a thin fallback wrapper over intDotsOrUndef
(DRY: one parseFloat+NaN+round body, not two).
Factory-signature unification:
- createSetupScriptHandlers(s), createLabelConfigHandlers(s, dpmm) and
createBarcodeHandlers(s) now take ParserState, matching the existing
createFieldHandlers/createGraphicsHandlers pattern.
- ^A dynamic-font dispatch fallback moves out of zplParser.ts into a
named export `handleDynamicFontA` in fields.ts. Orchestrator still
owns the `cmd[0]==='A'` wildcard match.
- New units.ts handler factory owns the ^MU command (both unitScale
format-state mutation and muResampling labelConfig persistence).
Schema reshape:
- formatDpi/outputDpi individual optionals → `muResampling: { formatDpi,
outputDpi } | undefined` paired sub-object. Type-enforces the
spec's "both or neither" invariant; parser persists only when both
slots arrive valid, generator emits the pair without half-set guards.
Correctness:
- Invalid ^MU a-slot (e.g. ^MUX) preserves prior unitScale instead
of silently downgrading to dots. Marked partial.
FormatState.unitScale documented as the one slot that intentionally
survives ^XA per ^MU spec; per-block isolation comes from
zplImportService calling parseZPL per ^XA block.
The ^A{font} dynamic-font command can't live in the exact-match
handler table because the font char is part of the command key.
Previously it leaked through as a named-export helper plus an
orchestrator-side inline `cmd[0]==='A' && cmd.length===2` check.
Introduces a `Wildcard` type (matches + handle) so the pattern-match
logic stays with its owning handler module. `createDynamicFontAWildcard`
in fields.ts now declares the pattern; the orchestrator collects
wildcards into a list and falls back to them only after exact-match
dispatch fails, so a registered handler always wins.
Handler signature gains `cmd: string` as a third argument so wildcards
can inspect the matched command key (e.g. read the font char from
`cmd[1]`). Existing handlers ignore the new arg.
Removes the named-export workaround; the orchestrator no longer needs
to know about specific wildcard patterns.
^BR p[2] is a 1-10 magnification multiplier, not a dot quantity. The
parser previously wrote it into `defaults.byModuleWidth` (the shared
^BY slot, dot-typed everywhere else), so a ^BR field would silently
overwrite real dot widths from ^BY and leak the magnification value
into subsequent non-gs1 barcodes.
Parser side:
- `FieldState.gsMagnification` (1-10 or undefined) carries ^BR p[2]
on its own slot. Reset at ^FS so a bare ^BR^FS doesn't leak.
- Out-of-range values fall back to `defaults.byModuleWidth` at flush
time, preserving the prior "single sizing knob" semantics when ^BY
and ^BR carry the same value (the common case).
Schema rename:
- `Gs1DatabarProps.moduleWidth` → `magnification`. The value always
represented the ^BR multiplier semantically; the rename ends the
confusion with the dot-typed ^BY value of the same name.
- Generator emits `magnification` to both ^BY and ^BR slots per the
Zebra convention. Imports with mismatched ^BY/^BR values normalize
to the ^BR slot (firmware behavior), so `^BY4 ^BR_,_,6` re-emits
as `^BY6 ^BR_,_,6`.
Schema migration:
- designFile.parseDesignFile and labelStore persist (v5→v6) walk the
object tree and rename `props.moduleWidth` → `props.magnification`
for gs1databar leaves. Both routes share a new `objectTree.ts`
walker. Migration is idempotent and preserves an existing
magnification key.
Locale rename:
- `registry.gs1databar.moduleWidth` → `registry.gs1databar.magnification`
across 32 locales. Display value seeded from aztec/qrcode.magnification
("Size (1-10)" in en) so the user-facing label matches how the rest
of the registry names the same 1-10 knob.
There was a problem hiding this comment.
Code Review
This pull request adds support for the ZPL ^MU (Units of Measure) command and refactors gs1databar barcode properties by renaming moduleWidth to magnification to clarify its role as a multiplier. It also introduces migration utilities to seamlessly upgrade legacy design files. The review feedback highlights two critical TypeScript compilation and runtime errors where the non-existent partialCmds property is accessed on s.result in both units.ts and fields.ts. Additionally, a medium-severity issue was identified in objectTree.ts where arrays could bypass type checks and be passed to the leaf visitor, which can be resolved by adding an explicit Array.isArray check.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Defensive: arrays satisfy `typeof node === "object"` so a caller passing an array directly would have fallen through to `visit(n)` with the array itself. Current callers iterate before recursing, so this is theoretical, but the guard removes the surprise.
No description provided.